home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turnbull China Bikeride
/
Turnbull China Bikeride - Disc 1.iso
/
ARGONET
/
PD
/
FILER
/
X-FILES.ZIP
/
057
/
!X-Files
/
c
/
cache
next >
Wrap
Text File
|
1997-01-22
|
23KB
|
962 lines
/* cache.c */
/*#define NOCACHE*/
#define NOWRITEBEHIND
#include "chunks.h"
#include "debug.h"
#include "module.h"
#include "swis.h"
#include <stdlib.h>
#include <string.h>
static int xFiles__timer;
#define xFiles_CACHECHUNKSIZE 1024
#define xFiles_OK2CACHE 4096
#define roundDown(x) ((x) & ~(xFiles_CACHECHUNKSIZE-1))
#define roundUp(x) roundDown((x) + xFiles_CACHECHUNKSIZE - 1)
typedef struct
{
int prev, next; /* Index into the array of cache chunks or -1 for NULL */
unsigned owner; /* (unsigned) pInfo for this chunk or 0 if unused */
unsigned offset; /* Offset in file where it started */
char *buffer;
BOOL dirty;
} xFiles_cacheItem;
static xFiles_cacheItem *cache;
static char *buffer;
static int cacheHead, cacheTail;
static int cacheSize, cacheUsed; /* elements that is */
void *xFiles_windowBuffer; /* shared by all and sundry */
int xFiles_windowBufferSize;
static BOOL xFiles__bufferClaimed;
#define _max(x, y) ((x) > (y) ? (x) : (y))
#define _min(x, y) ((x) < (y) ? (x) : (y))
static void xFiles__disposeCache(void)
{
if (cache)
{
free(cache);
cache = NULL;
}
if (buffer)
{
free(buffer);
buffer = NULL;
}
}
void xFiles_Tidy(void)
{
if (xFiles_windowBuffer)
{
free(xFiles_windowBuffer);
xFiles_windowBuffer = NULL;
}
xFiles__disposeCache();
}
_kernel_oserror *xFiles_releaseBuffer(void)
{
xFiles__bufferClaimed = FALSE;
return NULL;
}
_kernel_oserror *xFiles_claimBuffer(int notBiggerThan)
{
(void) notBiggerThan;
if (xFiles__bufferClaimed)
return &xFiles_BufferInUse;
xFiles__bufferClaimed = TRUE;
if (xFiles_windowBuffer)
return NULL;
if (xFiles_windowBuffer = malloc(xFiles_WINDOWSIZE), !xFiles_windowBuffer)
return &xFiles_NoMemory;
xFiles_windowBufferSize = xFiles_WINDOWSIZE;
return NULL;
}
static void xFiles_RemoveCacheItem(int item)
{
if (cache[item].prev != -1)
cache[cache[item].prev].next = cache[item].next;
else
cacheHead = cache[item].next;
if (cache[item].next != -1)
cache[cache[item].next].prev = cache[item].prev;
else
cacheTail = cache[item].prev;
cache[item].next =
cache[item].prev = -1;
}
static void xFiles_CacheAddAtHead(int item)
{
if (cacheHead != -1)
cache[cacheHead].prev = item;
else
cacheTail = item;
cache[item].prev = -1;
cache[item].next = cacheHead;
cacheHead = item;
}
static void xFiles_CacheAddAtTail(int item)
{
if (cacheTail != -1)
cache[cacheTail].next = item;
else
cacheHead = item;
cache[item].next = -1;
cache[item].prev = cacheTail;
cacheTail = item;
}
_kernel_oserror *xFiles_getLength(xFiles_info *pInfo, unsigned *pLength)
{
_kernel_swi_regs regs;
_kernel_oserror *err;
if (pInfo->fileSize == (unsigned) -1)
{
regs.r[0] = 2;
regs.r[1] = pInfo->fileHandle;
if (err = _kernel_swi(OS_Args, ®s, ®s), err)
return err;
pInfo->fileSize = (unsigned) regs.r[2];
}
if (pLength) *pLength = pInfo->fileSize;
return NULL;
}
_kernel_oserror *xFiles_setLength(xFiles_info *pInfo, unsigned Length)
{
_kernel_swi_regs regs;
_kernel_oserror *err;
int i;
unsigned owner = (unsigned) pInfo;
unsigned oldLength;
/*TRACE("xFiles_setLength(%p, %08x)\n", pInfo, Length);*/
if (err = xFiles_getLength(pInfo, &oldLength), err)
return err;
/* If the file is shrinking we need to discard any items which are cached
* for the bit of file which no longer exists, otherwise when the file
* grows again these bits will be mistaken for valid bits of file.
*/
if (Length < oldLength)
{
for (i = 0; i < cacheUsed; i++)
{
if (cache[i].owner == owner && cache[i].offset >= Length)
{
cache[i].offset = 0xFFFFFFFF;
cache[i].dirty = FALSE;
xFiles_RemoveCacheItem(i);
xFiles_CacheAddAtTail(i);
}
if (cache[i].offset == 0xFFFFFFFF)
cache[i].owner = i > 0 ? cache[i-1].owner + 1 : 0;
}
}
regs.r[0] = 3;
regs.r[1] = pInfo->fileHandle;
regs.r[2] = (int) Length;
if (err = _kernel_swi(OS_Args, ®s, ®s), err)
return err;
pInfo->fileSize = Length;
return NULL;
}
/* Read a glob of the file into the specified buffer.
* This is about as primitive as it gets.
*/
static _kernel_oserror *xFiles_rawRead(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
_kernel_swi_regs regs;
_kernel_oserror *err;
/*TRACE(" xFiles_rawRead(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/
regs.r[0] = 3; /* read bytes given pointer */
regs.r[1] = pInfo->fileHandle;
regs.r[2] = (int) pBuffer;
regs.r[3] = size;
regs.r[4] = pos;
if (err = _kernel_swi(OS_GBPB, ®s, ®s), err)
return err;
if (regs.r[3] > 0)
{
TRACE("*** %d bytes not read\n", regs.r[3]);
}
return NULL;
}
static _kernel_oserror *xFiles_rawWrite(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
_kernel_swi_regs regs;
/*TRACE(" xFiles_rawWrite(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/
regs.r[0] = 1; /* write bytes given pointer */
regs.r[1] = pInfo->fileHandle;
regs.r[2] = (int) pBuffer;
regs.r[3] = size;
regs.r[4] = pos;
return _kernel_swi(OS_GBPB, ®s, ®s);
}
static void xFiles_EmptyCache()
{
int i;
char *bp;
cacheHead =
cacheTail = -1;
/* Offsets within the buffer are statically associated with particular
* index entries, so this relationship is established here.
*/
for (i = 0, bp = buffer; i < cacheSize; i++, bp += xFiles_CACHECHUNKSIZE)
{
cache[i].prev =
cache[i].next = -1;
cache[i].owner = 0;
cache[i].offset = 0;
cache[i].buffer = bp;
cache[i].dirty = FALSE;
}
cacheUsed = 0;
}
static int xFiles_CacheCmp(unsigned owner, unsigned offset, int item)
{
return (owner == cache[item].owner) ? (offset - cache[item].offset)
: (owner - cache[item].owner);
}
static int xFiles_CacheLookup(xFiles_info *pInfo, unsigned offset)
{
unsigned owner = (unsigned) pInfo;
int lo, hi, mid, cmp;
for (lo = 0, hi = cacheUsed-1; lo <= hi; )
{
mid = (lo + hi) / 2;
cmp = xFiles_CacheCmp(owner, offset, mid);
if (cmp < 0)
hi = mid - 1;
else if (cmp > 0)
lo = mid + 1;
else
return mid;
}
return -1;
}
_kernel_oserror *xFiles_InitCache(unsigned size)
{
#ifdef NOCACHE
(void) size;
#else
cacheSize = size / xFiles_CACHECHUNKSIZE;
xFiles__disposeCache();
xFiles__timer = 0;
if (cacheSize == 0)
{
cache = NULL;
return NULL;
}
if (cache = malloc(sizeof(xFiles_cacheItem) * cacheSize), !cache)
goto fail;
if (buffer = malloc(xFiles_CACHECHUNKSIZE * cacheSize), !buffer)
goto fail;
xFiles_EmptyCache();
return NULL;
fail:
xFiles__disposeCache();
cacheSize = 0;
TRACE("Not enough space for cache; running with cache disabled");
#endif
return NULL;
}
_kernel_oserror *xFiles_FlushFileInfo(xFiles_info *pInfo)
{
int i;
unsigned owner = (unsigned) pInfo;
_kernel_oserror *err;
if (err = xFiles_Flush(), err)
return err;
for (i = 0; i < cacheUsed; i++)
{
if (cache[i].owner == owner)
{
cache[i].offset = 0xFFFFFFFF;
cache[i].dirty = FALSE;
xFiles_RemoveCacheItem(i);
xFiles_CacheAddAtTail(i);
}
if (cache[i].offset == 0xFFFFFFFF)
cache[i].owner = i > 0 ? cache[i-1].owner + 1 : 0;
}
return NULL;
}
static void xFiles_AdjustCache(int item, int by)
{
int i;
ASSERT(item != -1);
for (i = 0; i < cacheUsed; i++)
{
ASSERT(by > 0 || cache[i].prev != item);
ASSERT(by > 0 || cache[i].next != item);
if (cache[i].prev >= item)
cache[i].prev += by;
if (cache[i].next >= item)
cache[i].next += by;
}
ASSERT(by > 0 || cacheHead != item);
ASSERT(by > 0 || cacheTail != item);
if (cacheHead >= item)
cacheHead += by;
if (cacheTail >= item)
cacheTail += by;
}
static void xFiles_CacheCheck(void)
{
int prev, i, watchDog;
int broken = 0;
xFiles_info *pInfo;
if (!cache)
return;
if (cacheUsed > cacheSize)
goto trash;
prev = -1;
i = cacheHead;
watchDog = cacheUsed+1;
while (watchDog > 0 && i != -1)
{
if (cache[i].prev != prev)
{
TRACE("Cache back link is %d (should be %d)\n", cache[i].prev, prev);
broken++;
}
prev = i;
i = cache[i].next;
watchDog--;
}
if (watchDog == 0)
{
TRACE("Loop in cache links\n");
broken++;
}
else if (cacheTail != prev)
{
TRACE("cacheTail should is %d (should be %d)\n", cacheTail, prev);
broken++;
}
for (i = 0; i < cacheUsed-1; i++)
{
int cmp = xFiles_CacheCmp(cache[i].owner, cache[i].offset, i+1);
if (cmp >= 0)
{
TRACE("Cache items are not ordered/unique\n");
broken++;
break;
}
pInfo = (xFiles_info *) cache[i].owner;
if (cache[i].offset != 0xFFFFFFFF && cache[i].offset >= pInfo->fileSize)
{
TRACE("Cache item outside file's length (offset = %08x, fileSize = %08x)\n",
cache[i].offset, pInfo->fileSize);
broken++;
break;
}
}
if (broken == 0)
return;
trash:
TRACE("Cache is broken: discarding it\n");
TRACE("Head: %d, Tail: %d, Size: %d, Used: %d\n", cacheHead, cacheTail, cacheSize, cacheUsed);
for (i = 0; i < _min(cacheUsed, cacheSize); i++)
{
TRACE("%3d: %08x, %08x, %3d, %3d\n",
i, cache[i].owner, cache[i].offset, cache[i].prev, cache[i].next);
}
cacheUsed = 0;
cacheHead = cacheTail = -1;
}
/* Ensure the chunk with the specified offset is in the cache, returning it's index.
* If the chunk isn't in the cache it will be loaded from the file replacing the
* least recently used item in the cache.
*/
static _kernel_oserror *xFiles_CacheEnsure(xFiles_info *pInfo, unsigned offset, int *pItem, BOOL noRead)
{
int item;
unsigned owner = (unsigned) pInfo;
_kernel_oserror *err;
xFiles_cacheItem tmp;
ASSERT(offset == roundDown(offset));
ASSERT(cacheUsed <= cacheSize);
if (item = xFiles_CacheLookup(pInfo, offset), item != -1)
{
/*TRACE("%p, %08x -- cache hit\n", pInfo, offset);*/
xFiles_RemoveCacheItem(item);
xFiles_CacheAddAtHead(item);
*pItem = item;
return NULL;
}
/*TRACE("%p, %08x -- cache miss\n", pInfo, offset);*/
/* If the cache is full get rid of a chunk. What actually happens is that the chunk to
* be discarded is moved up to the end of the cache. We can't actually throw it away
* because each chunk refers to its own bit of buffer and that only gets allocated once.
*/
if (cacheUsed == cacheSize)
{
item = cacheTail;
ASSERT(cacheTail != -1);
if (cache[item].dirty)
{
if (cache[item].offset != 0xFFFFFFFF)
{
/*TRACE("Flush(1) %08x, %08x, %08x\n",
cache[item].owner, cache[item].buffer, cache[item].offset);*/
if (err = xFiles_rawWrite((xFiles_info *) cache[item].owner,
cache[item].buffer, cache[item].offset,
xFiles_CACHECHUNKSIZE), err)
return err;
}
cache[item].dirty = FALSE;
}
tmp = cache[item]; /* save it */
/*TRACE(" discarding item %d (%08x, %08x)\n",
item, cache[item].owner, cache[item].offset);*/
xFiles_RemoveCacheItem(item); /* remove from the linked list */
cacheUsed--;
memmove(&cache[item], &cache[item+1], sizeof(xFiles_cacheItem) * (cacheUsed - item));
xFiles_AdjustCache(item, -1);
cache[cacheUsed] = tmp;
}
for (item = 0; item < cacheUsed && xFiles_CacheCmp(owner, offset, item) > 0; item++)
;
/*TRACE(" inserting as item %d\n", item);*/
tmp = cache[cacheUsed]; /* Get the unused one at the end */
if (item < cacheUsed)
memmove(&cache[item+1], &cache[item], sizeof(xFiles_cacheItem) * (cacheUsed - item));
cacheUsed++;
xFiles_AdjustCache(item, 1);
cache[item] = tmp;
xFiles_CacheAddAtHead(item);
cache[item].owner = owner;
cache[item].offset = offset;
cache[item].dirty = FALSE;
if (!(item <= 0 || xFiles_CacheCmp(cache[item-1].owner, cache[item-1].offset, item) < 0) ||
!(item >= cacheUsed-1 || xFiles_CacheCmp(cache[item+1].owner, cache[item+1].offset, item) > 0))
{
unsigned i;
for (i = 0; i < cacheUsed; i++)
{
TRACE("%3d: %08x, %08x, %3d, %3d%s\n", i, cache[item].owner, cache[item].offset,
cache[item].prev, cache[item].next,
i == item ? " *" : "");
}
TRACE("xFiles_CacheEnsure(%p, %08x, %p, %d)\n", pInfo, offset, pItem, noRead);
TRACE("Destroying cache\n");
cacheUsed = 1; /* just destroy it */
cacheHead = cacheTail = 0;
cache[0].prev = cache[0].next = -1;
item = 0;
}
*pItem = item;
if (noRead)
return NULL;
return xFiles_rawRead(pInfo, cache[item].buffer, offset, xFiles_CACHECHUNKSIZE);
}
_kernel_oserror *xFiles_read(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
_kernel_oserror *err;
int item;
unsigned start, end;
unsigned bufPos;
char *outPtr;
unsigned fragSize, totalSize;
unsigned pendingStart, pendingSize;
#ifndef NOCACHE
if (!cache)
#endif
return xFiles_rawRead(pInfo, pBuffer, pos, size);
xFiles_CacheCheck();
/*TRACE("xFiles_read(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/
if (size == 0)
return NULL;
/* Set about reading it through the cache */
start = roundDown(pos);
end = roundUp(pos + size);
bufPos = pos - start;
outPtr = (char *) pBuffer;
totalSize = size;
ASSERT(start < end);
ASSERT(bufPos < xFiles_CACHECHUNKSIZE);
if (size > xFiles_OK2CACHE)
{
/* Large transfer: check each component block for a cache hit */
pendingStart = pos;
pendingSize = 0;
while (start < end)
{
item = xFiles_CacheLookup(pInfo, start);
fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
if (item != -1)
{
if (pendingSize != 0)
{
if (err = xFiles_rawRead(pInfo, outPtr, pendingStart, pendingSize), err)
return err;
pendingStart += pendingSize;
outPtr += pendingSize;
pendingSize = 0;
}
ASSERT(cache[item].buffer >= buffer);
ASSERT(cache[item].buffer < buffer + xFiles_CACHECHUNKSIZE * cacheSize);
memcpy(outPtr, cache[item].buffer + bufPos, fragSize);
outPtr += fragSize;
pendingStart += fragSize;
}
else
pendingSize += fragSize;
totalSize -= fragSize;
start += xFiles_CACHECHUNKSIZE;
bufPos = 0;
}
if (pendingSize != 0)
{
if (err = xFiles_rawRead(pInfo, outPtr, pendingStart, pendingSize), err)
return err;
}
}
else
{
while (start < end)
{
/*TRACE(" reading chunk at %08x through cache\n", start);*/
if (err = xFiles_CacheEnsure(pInfo, start, &item, FALSE), err)
return err;
fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
ASSERT(outPtr >= (char *) pBuffer);
ASSERT(outPtr + fragSize <= (char *) pBuffer + size);
memcpy(outPtr, cache[item].buffer + bufPos, fragSize);
outPtr += fragSize;
totalSize -= fragSize;
start += xFiles_CACHECHUNKSIZE;
bufPos = 0;
}
}
#ifndef NOWRITEBEHIND
xFiles__timer = 200;
#endif
return NULL;
}
_kernel_oserror *xFiles_write(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
int item;
unsigned start, end;
unsigned bufPos;
char *outPtr;
unsigned fragSize;
unsigned totalSize;
unsigned fileSize;
BOOL noRead;
_kernel_oserror *err;
unsigned pendingStart, pendingSize;
xFiles_CacheCheck();
/*TRACE("xFiles_write(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/
if (size == 0)
return NULL;
#ifndef NOCACHE
if (!cache)
#endif
return xFiles_rawWrite(pInfo, pBuffer, pos, size);
start = roundDown(pos);
end = roundUp(pos + size);
bufPos = pos - start;
outPtr = (char *) pBuffer;
totalSize = size;
ASSERT(start < end);
ASSERT(bufPos < xFiles_CACHECHUNKSIZE);
if (size > xFiles_OK2CACHE)
{
/* Large transfer: check each component block for a cache hit and update
* the cache too if necessary (bus snooping).
*/
pendingStart = pos;
pendingSize = 0;
while (start < end)
{
item = xFiles_CacheLookup(pInfo, start);
fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
if (item != -1)
{
if (pendingSize != 0)
{
if (err = xFiles_rawWrite(pInfo, outPtr, pendingStart, pendingSize), err)
return err;
pendingStart += pendingSize;
outPtr += pendingSize;
pendingSize = 0;
}
ASSERT(cache[item].buffer >= buffer);
ASSERT(cache[item].buffer < buffer + xFiles_CACHECHUNKSIZE * cacheSize);
memcpy(cache[item].buffer + bufPos, outPtr, fragSize);
outPtr += fragSize;
pendingStart += fragSize;
cache[item].dirty = TRUE;
}
else
pendingSize += fragSize;
totalSize -= fragSize;
start += xFiles_CACHECHUNKSIZE;
bufPos = 0;
}
if (pendingSize != 0)
{
if (err = xFiles_rawWrite(pInfo, outPtr, pendingStart, pendingSize), err)
return err;
}
}
else
{
if (err = xFiles_getLength(pInfo, &fileSize), err)
return err;
if (pos + size > fileSize)
{
fileSize = xFiles_roundUp(pInfo, pos + size);
if (err = xFiles_setLength(pInfo, fileSize), err)
return err;
}
/* Small transfer: ensure each component block is cached and write to both
* the cached block and to disc.
*/
while (start < end)
{
/* Work out whether we need to read before write */
fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
noRead = (bufPos == 0 && fragSize == xFiles_CACHECHUNKSIZE);
/*TRACE(" writing chunk at %08x through cache (fragSize = %08x, noRead = %s)\n",
start, fragSize, noRead ? "TRUE" : "FALSE");*/
if (err = xFiles_CacheEnsure(pInfo, start, &item, noRead), err)
return err;
ASSERT(outPtr >= (char *) pBuffer);
ASSERT(outPtr + fragSize <= (char *) pBuffer + size);
memcpy(cache[item].buffer + bufPos, outPtr, fragSize);
#if 1
cache[item].dirty = TRUE;
#else
if (err = xFiles_rawWrite(pInfo, cache[item].buffer, cache[item].offset, xFiles_CACHECHUNKSIZE), err)
return err;
#endif
outPtr += fragSize;
totalSize -= fragSize;
start += xFiles_CACHECHUNKSIZE;
bufPos = 0;
}
}
#ifndef NOWRITEBEHIND
xFiles__timer = 200;
#endif
return NULL;
}
_kernel_oserror *xFiles_Commit(xFiles_info *pInfo)
{
_kernel_oserror *err;
if (err = xFiles_squashFile(pInfo, FALSE), err)
return err;
#ifdef NOWRITEBEHIND
if (err = xFiles_Flush(), err)
return err;
#endif
return NULL;
}
/* Force the image file to truncate by
* issuing the appropriate service call
*/
_kernel_oserror *xFiles_Truncate(xFiles_info *pInfo)
{
_kernel_oserror *err;
_kernel_swi_regs regs;
char name[257];
if (pInfo->openList.head != NULL)
{
/*TRACE("Don't want to truncate image which has open files\n");*/
return NULL;
}
regs.r[0] = 7;
regs.r[1] = pInfo->fileHandle;
regs.r[2] = (int) name;
regs.r[5] = 256;
if (err = _kernel_swi(OS_Args, ®s, ®s), err)
return err;
/* Now force the named image to close. This will cause X-Files to reenter, so
* be ready!
*/
/*TRACE("Closing %s\n", name);*/
regs.r[1] = 0x68;
regs.r[2] = (int) name;
regs.r[3] = 0;
if (err = _kernel_swi(OS_ServiceCall, ®s, ®s), err)
return err;
return NULL;
}
_kernel_oserror *xFiles_Flush(void)
{
int i;
_kernel_oserror *err;
_kernel_swi_regs regs;
xFiles_info *pInfo;
for (i = 0; i < cacheUsed; i++)
{
if (cache[i].offset != 0xFFFFFFFF && cache[i].dirty)
{
/*TRACE("Flush(2) %08x, %08x, %08x\n",
cache[i].owner, cache[i].buffer, cache[i].offset);*/
if (err = xFiles_rawWrite((xFiles_info *) cache[i].owner,
cache[i].buffer, cache[i].offset,
xFiles_CACHECHUNKSIZE), err)
return err;
cache[i].dirty = FALSE;
}
}
for (pInfo = (xFiles_info *) xFiles_openFiles.head; pInfo; pInfo = (xFiles_info *) pInfo->li.next)
{
regs.r[0] = 255;
regs.r[1] = pInfo->fileHandle;
if (err = _kernel_swi(OS_Args, ®s, ®s), err)
return err;
}
return NULL;
}
_kernel_oserror *xFiles_WriteBehind(_kernel_swi_regs *regs)
{
xFiles_info *pInfo, *pNext;
(void) regs;
#ifndef NOWRITEBEHIND
(void) xFiles_Flush();
for (pInfo = (xFiles_info *) xFiles_openFiles.head; pInfo; pInfo = pNext)
{
pNext = (xFiles_info *) pInfo->li.next;
if (pInfo->flags & xFiles_fNeedTruncate)
{
pInfo->flags &= ~xFiles_fNeedTruncate;
(void) xFiles_Truncate(pInfo);
/* Beware: pInfo might not be valid now */
}
}
#endif
return NULL;
}
extern void entry_WriteBehind(void);
#pragma -s1
_kernel_oserror *xFiles_Ticker(_kernel_swi_regs *regs)
{
(void) regs;
if (xFiles__timer > 0 && --xFiles__timer == 0)
{
_kernel_swi_regs regs;
regs.r[0] = (int) entry_WriteBehind;
regs.r[1] = (int) wsp;
(void) _kernel_swi(OS_AddCallBack, ®s, ®s);
}
return NULL;
}
#pragma -s0